/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/

/**
 * @brief  Main module, AMCC driver.
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */


#include <stdio.h>
#include <string.h>

#include "amcc_main.h"


// forward declaration

static int  init_run_env  (t_workdata_ptr, int argc, char * argv[]);
static int  clear_run_env (t_workdata_ptr);
static int  perform_workflow (t_workdata_ptr);


// Main entry of AMCC.
//
// The command-line-arguments are defined by print_usage()
//     and parse_command_line() in cmd_args.c.
//
// Return value:
//   0:  Everything is OK.
//   1:  Bad usage.
//   2:  There are some errors during the translation.
//       All errors are shown in stdout and log-file.
//
int main (int argc, char * argv[]) {
    t_workdata  workdata;

    if ( init_run_env (& workdata, argc, argv) != 0 )
        return 1;

    SHOW_MSG("Analyzing [%s] ...\n", amcc_get_inputFilePath());

    perform_workflow ( & workdata );

    SHOW_MSG("\nAnalysis finished for [%s].\n", amcc_get_inputFilePath());
    SHOW_MSG("Please read files [%s/*.*] for more details.\n",
             amcc_get_outputDir());

    clear_run_env (& workdata);

    return (error_get_count() > 0) ? 2 : 0;
}


// Initialize run environment for AMCC
static int  init_run_env (t_workdata_ptr pWorkdata, int argc, char * argv[]) {
    memset(pWorkdata, 0, sizeof(*pWorkdata));
    amcc_boot0 (AMCC_PRJ_NAME);

    SHOW_MSG("Welcome to " AMCC_PRJ_NAME
           " v" AMCC_VERSION_STRING
           " from %s.\n%s.\n" ,
           amcc_organization(), amcc_build_info());

    char * pWorkDir = path_get_workdir(NULL, 0);
    SHOW_MSG("Current working directory is [%s].\n\n", pWorkDir);
    free(pWorkDir);

    pWorkdata->targetPhase = 100;
    if ( parse_command_line(pWorkdata, argc, argv) != 0 ) {
        print_usage( argv[0] );
        return 1;
    }

    amcc_bootup ();
    return 0;
}


// Clear run environment, shutdown AMCC
static int  clear_run_env (t_workdata_ptr pWorkdata) {

    log_info("Now destroying phase products...", AMCC_PRJ_NAME);

    ircodes_destroy( pWorkdata->programCodes );
    symtable_destroy( pWorkdata->pSymTableRoot );
    parse_tree_destroy( pWorkdata->pParseTreeRoot );
    tokenstream_destroy(pWorkdata->pTokenStream );

    log_info("Phase products destroyed.", AMCC_PRJ_NAME);

    amcc_shutdown();
    return 0;
}


static int  do_lexical_analysis (t_workdata_ptr  pWorkdata) {
    t_lexer_ptr pLexer = lexer_new(amcc_get_inputFilePath());
    int retcode = lexer_run( pLexer );
    t_tokenstream_ptr pTokStream = lexer_GRAB_tokenstream( pLexer );
    lexer_destroy( pLexer );

    // Generates the token file
    tokenstream_dump( pTokStream );

    pWorkdata->pTokenStream = pTokStream;
    return retcode;
}


static int  do_syntax_analysis (t_workdata_ptr  pWorkdata) {
    t_parser_ptr pParser = parser_new(pWorkdata->pTokenStream);
    int retcode = parser_run( pParser );
    t_tree_ptr pParseTreeRoot = parser_GRAB_tree( pParser );
    parser_destroy(pParser);

    // Generates the tree file
    tree_dump(pParseTreeRoot);

    pWorkdata->pParseTreeRoot = pParseTreeRoot;
    return retcode;

}


static int  do_symbol_table_build (t_workdata_ptr  pWorkdata) {
    t_symtable_builder * pBuilder = symtable_builder_new(pWorkdata->pParseTreeRoot);
    int retcode = symtable_builder_run(pBuilder );
    t_symtable_ptr pSymTableRoot = symtable_builder_GRAB(pBuilder);
    bool hasError = symtable_builder_errors( pBuilder );
    symtable_builder_destroy(pBuilder );

    if (hasError == 0) {
        // Generates the symbol-table file
        symtable_dump(pSymTableRoot);

        // Refreshes the tree file to check/look symbol entries info
        tree_dump(pWorkdata->pParseTreeRoot);
    }

    pWorkdata->pSymTableRoot = pSymTableRoot;
    return  retcode;
}


static int  do_ircode_generation (t_workdata_ptr  pWorkdata) {
    t_emitter_ptr  pEmitter = ircode_emitter_new(pWorkdata->pParseTreeRoot,
                                                 pWorkdata->pSymTableRoot);
    int retcode = ircode_emitter_run(pEmitter);
    t_program_codes programCodes = ircode_emitter_GRAB(pEmitter);
    ircode_emitter_destroy(pEmitter);

    ircode_dump(programCodes); // Generates the IR-codes file

    pWorkdata->programCodes = programCodes;
    return retcode;
}



struct amcc_work {
    const char * name;
    int (* functor) (t_workdata_ptr);
} amcc_workflows [] = {
      { "lexical analysis"   ,  do_lexical_analysis   }
    , { "syntax analysis"    ,  do_syntax_analysis    }
    , { "symbol-table build" ,  do_symbol_table_build }
    , { "ir-code generation" ,  do_ircode_generation  }
};


// Perform all business of AMCC
static int  perform_workflow (t_workdata_ptr  pWorkdata) {
    int   result = 0 ;
    int  nErrors = 0 ;
    int  nPhases = sizeof(amcc_workflows) / sizeof(amcc_workflows[0]);

    if (pWorkdata->targetPhase > nPhases)
        pWorkdata->targetPhase = nPhases;

    log_info("Hi, you want to execute %d phase%s",  pWorkdata->targetPhase,
             (pWorkdata->targetPhase > 1) ? "s" : "" );
    log_info("Begin analysis [%s] ...\n", amcc_get_inputFilePath());

    for( int  i = 0 ; i < nPhases; ++ i ) {
        if (i >= pWorkdata->targetPhase) break;

        SHOW_MSG("Performing [%s] ...\n", amcc_workflows[i].name);
        log_info("Performing phase [%s] ...", amcc_workflows[i].name);

        result  = amcc_workflows[i].functor (pWorkdata);
        nErrors = error_get_count();

        SHOW_MSG("%.*sFinished [%s] with code %d, found %d error%s.\n",
                 (nErrors>0 || result != 0 ? 0 : 2),  "     ",
                 amcc_workflows[i].name ,   result,
                 nErrors,  (nErrors>1) ? "s" : "" );
        log_info("Finished phase [%s] with code %d, %d error%s.\n",
                 amcc_workflows[i].name ,   result,
                 nErrors,  (nErrors>1) ? "s" : "" );

        if (0 != result || nErrors > 0)
            break;
    }

    log_info("Analysis finished %d phase%s for [%s].",
                pWorkdata->targetPhase,
                pWorkdata->targetPhase > 1 ? "s" : "" ,
                amcc_get_inputFilePath()
            );

    return result;
}
